/*
 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package me.ahoo.cosid;

import com.google.errorprone.annotations.ThreadSafe;
import jakarta.annotation.Nonnull;

/**
 * Integer ID generator that adapts a long-based ID generator to produce integer IDs.
 *
 * <p>This class wraps an existing {@link IdGenerator} that produces long IDs and
 * provides methods to generate integer IDs instead. This is useful when working
 * with systems or databases that require integer primary keys rather than longs.
 *
 * <p>Note that integer IDs have a much smaller range than long IDs (approximately
 * +/- 2.1 billion vs +/- 9.2 quintillion), so this generator throws an
 * {@link IdOverflowException} when the underlying long ID exceeds the integer range.
 *
 * <p>This class implements {@link StringIdGenerator} to provide string ID generation
 * capabilities as well, using the wrapped generator's ID converter.
 *
 * <p>Implementations of this class are thread-safe and can be used concurrently
 * across multiple threads.
 *
 * @author ahoo wang
 */
@ThreadSafe
public class IntegerIdGenerator implements StringIdGenerator {

    /**
     * The actual ID generator that produces long IDs.
     *
     * <p>This generator is used as the source of IDs, which are then converted
     * to integer format when requested.
     */
    protected final IdGenerator actual;

    /**
     * Create a new IntegerIdGenerator that wraps the specified long ID generator.
     *
     * <p>The provided generator will be used to produce the underlying long IDs
     * that are then converted to integers when requested.
     *
     * @param actual The actual ID generator that produces long IDs
     */
    public IntegerIdGenerator(IdGenerator actual) {
        this.actual = actual;
    }

    /**
     * Generate a distributed ID as an integer value.
     *
     * <p>This method generates a unique integer identifier by first generating
     * a long ID from the wrapped generator and then converting it to an integer.
     *
     * <p>If the generated long ID exceeds the range of valid integer values
     * (Integer.MIN_VALUE to Integer.MAX_VALUE), an {@link IdOverflowException}
     * is thrown.
     *
     * @return A unique distributed ID as an integer value
     * @throws IdOverflowException if the generated long ID exceeds the integer range
     */
    public int generate() throws IdOverflowException {
        long id = actual.generate();
        ensureInteger(id);
        return (int) id;
    }

    /**
     * Generate a distributed ID as a string value.
     *
     * <p>This method generates a unique string identifier by first generating
     * a long ID from the wrapped generator, ensuring it fits in an integer,
     * and then converting it to a string using the wrapped generator's ID converter.
     *
     * <p>If the generated long ID exceeds the range of valid integer values
     * (Integer.MIN_VALUE to Integer.MAX_VALUE), an {@link IdOverflowException}
     * is thrown.
     *
     * @return A unique distributed ID as a string value
     * @throws IdOverflowException if the generated long ID exceeds the integer range
     */
    @Nonnull
    @Override
    public String generateAsString() throws IdOverflowException {
        long id = actual.generate();
        ensureInteger(id);
        return actual.idConverter().asString(id);
    }

    /**
     * Ensure that the specified long ID fits within the integer value range.
     *
     * <p>This method checks if the provided long ID is within the valid range
     * for integer values (Integer.MIN_VALUE to Integer.MAX_VALUE). If not, it
     * throws an {@link IdOverflowException}.
     *
     * @param id The long ID to check
     * @throws IdOverflowException if the ID exceeds the integer range
     */
    private void ensureInteger(long id) throws IdOverflowException {
        if (id < Integer.MIN_VALUE || id > Integer.MAX_VALUE) {
            throw new IdOverflowException(id);
        }
    }

    /**
     * Exception thrown when an ID exceeds the integer value range.
     *
     * <p>This exception is thrown by {@link IntegerIdGenerator} when the underlying
     * long ID generated by the wrapped generator exceeds the range of valid
     * integer values (approximately +/- 2.1 billion).
     */
    public static class IdOverflowException extends CosIdException {
        private final long id;

        /**
         * Create a new IdOverflowException for the specified ID.
         *
         * @param id The ID that exceeded the integer range
         */
        public IdOverflowException(long id) {
            super("id [" + id + "] overflow.");
            this.id = id;
        }

        /**
         * Get the ID that caused the overflow.
         *
         * @return The ID that exceeded the integer range
         */
        public long getId() {
            return id;
        }
    }
}
